/*
    libhdvbd - Library for building sophisticated DVB applications
    Copyright (C) 2012 Tony Houghton <h@realh.co.uk>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <glib.h>

#include "execq.h"

struct _HdvbExecQ {
    GAsyncQueue *queue;
    GThread *thread;
};

typedef struct {
    HdvbExecQCallback callback;
    gpointer data1, data2, data3;
    gboolean block;
    GCond cond;
    GMutex mutex;
} HdvbExecQItem;

static void
hdvb_exec_q_free_item(HdvbExecQItem *item)
{
    if (item->block)
    {
        g_cond_clear(&item->cond);
        g_mutex_clear(&item->mutex);
    }
    g_slice_free(HdvbExecQItem, item);
}

static gpointer
hdvb_exec_q_thread(gpointer gq)
{
    HdvbExecQ *q = gq;

    while (TRUE)
    {
        HdvbExecQItem *item = g_async_queue_pop(q->queue);

        if (!item->callback)
        {
            g_slice_free(HdvbExecQItem, item);
            break;
        }
        item->callback(item->data1, item->data2, item->data3);
        if (item->block)
        {
            g_mutex_lock(&item->mutex);
            g_cond_signal(&item->cond);
            g_mutex_unlock(&item->mutex);
            /* Client frees item */
        }
        else
        {
            hdvb_exec_q_free_item(item);
        }
    }
    return NULL;
}

HdvbExecQ *
hdvb_exec_q_new(void)
{
    HdvbExecQ *q = g_slice_new(HdvbExecQ);

    q->queue = g_async_queue_new();
    q->thread = g_thread_new("exec-q", hdvb_exec_q_thread, q);

    return q;
}

void
hdvb_exec_q_add_call(HdvbExecQ *q, HdvbExecQCallback callback,
        gpointer data1, gpointer data2, gpointer data3, gboolean block)
{
    HdvbExecQItem *item = g_slice_new(HdvbExecQItem);

    item->callback = callback;
    item->data1 = data1;
    item->data2 = data2;
    item->data3 = data3;
    item->block = block;
    if (block)
    {
        g_cond_init(&item->cond);
        g_mutex_init(&item->mutex);
        g_mutex_lock(&item->mutex);
    }
    g_async_queue_push(q->queue, item);
    if (block)
    {
        g_cond_wait(&item->cond, &item->mutex);
        g_mutex_unlock(&item->mutex);
        hdvb_exec_q_free_item(item);
    }
    /* If not blocking, queue's thread deletes item */
}

void
hdvb_exec_q_delete(HdvbExecQ *q)
{
    HdvbExecQItem *item = g_slice_new(HdvbExecQItem);

    item->callback = NULL;
    g_async_queue_push(q->queue, item);
    g_thread_join(q->thread);
    g_slice_free(HdvbExecQ, q);
}

